/*
 * Copyright 2008-2009 Aying.Net Powered by Fuchun.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.aying.util;

import java.io.File;
import java.util.Iterator;
import java.util.Collection;

import net.aying.util.StringUtils;

/**
 * 包含文件名称、路径等常用处理方法的便捷工具类。
 * 
 * @author Fuchun
 * @version 1.0, 2009-07-27
 */
public class FilenameUtils extends StringUtils {

    /** {@code Unix} 操作系统文件夹分隔符。 */
    public static final char UNIX_SEPARATOR = '/';

    /** {@code Windows} 操作系统文件夹分隔符。 */
    public static final char WINDOWS_SEPARATOR = '\\';

    /** 与系统有关的默认名称分隔符。 */
    public static final char SYSTEM_SEPARATOR = File.separatorChar;

    /** 文件扩展名分隔符。 */
    public static final char EXTENSION_SEPARATOR = '.';

    /** 文件扩展名分隔符的字符串形式。 */
    public static final String EXTENSION_SEPARATOR_STR = String.valueOf(EXTENSION_SEPARATOR);

    /** 和当前系统相反的分隔符。 */
    public static final char OTHER_SEPARATOR;

    static {
        if (isWindows())
            OTHER_SEPARATOR = UNIX_SEPARATOR;
        else
            OTHER_SEPARATOR = WINDOWS_SEPARATOR;
    }

    /**
     * 测试当前系统是否使用 {@code Windows} 的路径分隔符。
     * 
     * @return 若当前系统使用 {@code Windows} 的路径分隔符，则返回 {@code true}，否则返回
     *         {@code false}。
     */
    static boolean isWindows() {
        return SYSTEM_SEPARATOR == WINDOWS_SEPARATOR;
    }

    /**
     * 测试给定的字符是否是一个分隔符。
     * 
     * @param ch 给定的字符。
     * @return 若给定的字符是一个分隔符，则返回 {@code true}，否则返回 {@code false}。
     */
    public static boolean isSeparator(char ch) {
        return (UNIX_SEPARATOR == ch) || (WINDOWS_SEPARATOR == ch);
    }

    /**
     * 将给定的路径中的文件夹分隔符全部转换为 {@code Unix} 系统使用的系统分隔符。
     * 
     * @param path 要转换的路径。
     * @return 新的路径字符串。
     */
    public static String toUnixSeparators(String path) {
        if (path == null || path.indexOf(WINDOWS_SEPARATOR) == -1) return path;
        return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR);
    }

    /**
     * 将给定的路径中的文件夹分隔符全部转换为 {@code Windows} 系统使用的系统分隔符。
     * 
     * @param path 要转换的路径。
     * @return 新的路径字符串。
     */
    public static String toWindowsSeparators(String path) {
        if (path == null || path.indexOf(UNIX_SEPARATOR) == -1) return path;
        return path.replace(UNIX_SEPARATOR, WINDOWS_SEPARATOR);
    }

    /**
     * 将给定的路径中的文件夹分隔符全部转换为当前系统使用的系统分隔符。
     * 
     * @param path 要转换的路径。
     * @return 新的路径字符串。
     */
    public static String toSystemSeparators(String path) {
        if (path == null) return null;
        if (isWindows())
            return toWindowsSeparators(path);
        else
            return toUnixSeparators(path);
    }
    
    /**
     * 返回给定的文件路径的前缀长度。如：{@code C:\、~/} 等
     * <p />
     * 该方法将自动分析并区分 {@code Unix} 和 {@code Windows} 操作系统。
     * 
     * <pre>
     * Windows:
     * a\b\c.txt       --&gt; ""             -- 相对路径
     * \a\b\c.txt      --&gt; "\\"           -- 绝对路径
     * C:a\b\c.txt     --&gt; "C:"           -- 相对的驱动盘符
     * C:\a\b\c.txt    --&gt; "C:\\"         -- 绝对的驱动盘符
     * \\server\a\b\c  --&gt; "\\\\server\\" -- UNC
     * 
     * Unix:
     * a/b/c.txt           --> ""          --> 相对路径
     * /a/b/c.txt          --> "/"         --> 绝对路径
     * ~/a/b/c.txt         --> "~/"        --> 当前用户路径
     * ~                   --> "~/"        --> 当前用户路径 (添加了斜线分隔符)
     * ~user/a/b/c.txt     --> "~user/"    --> user 的用户路径
     * ~user               --> "~user/"    --> user 的用户路径 (添加了斜线分隔符)
     * </pre>
     * 
     * @param filename 给定的全文件路径。
     * @return 给定的文件路径的前缀长度。
     */
    public static int getPrefixLength(String filename) {
        if(filename == null) {
            return -1;
        }
        int len = filename.length();
        if(len == 0) {
            return 0;
        }
        char first = filename.charAt(0);
        if(first == ':') {
            return -1;
        }
        if(len == 1) {
            if(first == '~') {
                return 2;
            }
            return isSeparator(first) ? 1 : 0;
        } else {
            if(first == '~') {
                int posUnix = filename.indexOf(UNIX_SEPARATOR, 1);
                int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1);
                if(posUnix == -1 && posWin == -1) {
                    return len + 1;
                }
                posUnix = (posUnix == -1 ? posWin : posUnix);
                posWin = (posWin == -1 ? posUnix : posWin);
                return Math.max(posUnix, posWin) + 1;
            }
            char sec = filename.charAt(1);
            if(sec == ':') {
                first = Character.toUpperCase(first);
                if(first >= 'A' && first <= 'Z') {
                    if(len == 2 || isSeparator(filename.charAt(2)) == false) {
                        return 2;
                    }
                    return 3;
                }
                return -1;
            } else if(isSeparator(first) && isSeparator(sec)) {
                int posUnix = filename.indexOf(UNIX_SEPARATOR, 2);
                int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2);
                if((posUnix == -1 && posWin == -1) || posUnix == 2 || posWin == 2) {
                    return -1;
                }
                posUnix = (posUnix == -1 ? posWin : posUnix);
                posWin = (posWin == -1 ? posUnix : posWin);
                return Math.max(posUnix, posWin) + 1;
            } else {
                return isSeparator(first) ? 1 : 0;
            }
        }
    }
    
    /**
     * 返回给定的文件路径的前缀。如：{@code C:\、~/} 等
     * <p />
     * 该方法将自动分析并区分 {@code Unix} 和 {@code Windows} 操作系统。
     * 
     * <pre>
     * Windows:
     * a\b\c.txt       --&gt; ""             -- 相对路径
     * \a\b\c.txt      --&gt; "\\"           -- 绝对路径
     * C:a\b\c.txt     --&gt; "C:"           -- 相对的驱动盘符
     * C:\a\b\c.txt    --&gt; "C:\\"         -- 绝对的驱动盘符
     * \\server\a\b\c  --&gt; "\\\\server\\" -- UNC
     * 
     * Unix:
     * a/b/c.txt           --> ""          --> 相对路径
     * /a/b/c.txt          --> "/"         --> 绝对路径
     * ~/a/b/c.txt         --> "~/"        --> 当前用户路径
     * ~                   --> "~/"        --> 当前用户路径 (添加了斜线分隔符)
     * ~user/a/b/c.txt     --> "~user/"    --> user 的用户路径
     * ~user               --> "~user/"    --> user 的用户路径 (添加了斜线分隔符)
     * </pre>
     * 
     * @param filename 给定的全文件路径。
     * @return 给定的文件路径的前缀
     */
    public static String getPrefix(String filename) {
        if(filename == null) {
            return null;
        }
        int len = getPrefixLength(filename);
        if(len < 0) {
            return null;
        }
        if(len > filename.length()) {
            // 只可能存在于 Unix 操作系统
            return filename + UNIX_SEPARATOR;
        }
        return filename.substring(0, len);
    }
    
    /**
     * 返回给定全路径名的去除前缀的路径。
     * <p />
     * 该方法返回的路径结尾将自动补齐目录分隔符。
     * 
     * <pre>
     * C:\a\b\c.txt  --&gt; "a\\b\\"
     * ~/a/b/c.txt   --&gt; "a/b/"
     * a.txt         --&gt; ""
     * a/b/c         --&gt; "a/b/"
     * a/b/c/        --&gt; "a/b/c/"
     * </pre>
     * 
     * @param filename 给定的全路径名。
     * @return 给定全路径名的去除前缀的路径。
     */
    public static String getPath(String filename) {
        return doGetPath(filename, 1);
    }
    
    /**
     * 返回给定全路径名的去除前缀的路径。
     * <p />
     * 该方法返回的路径结尾将省略目录分隔符。
     * 
     * <pre>
     * C:\a\b\c.txt  --&gt; "a\\b"
     * ~/a/b/c.txt   --&gt; "a/b"
     * a.txt         --&gt; ""
     * a/b/c         --&gt; "a/b"
     * a/b/c/        --&gt; "a/b/c"
     * </pre>
     * 
     * @param filename 给定的全路径名。
     * @return 给定全路径名的去除前缀的路径。
     */
    public static String getPathNoEndSeperator(String filename) {
        return doGetPath(filename, 0);
    }
    
    private static String doGetPath(String filename, int seperatorAdd) {
        if(filename == null) {
            return null;
        }
        int prefix = getPrefixLength(filename);
        if(prefix < 0) {
            return null;
        }
        int index = lastIndexOfSeperator(filename);
        if(prefix >= filename.length() || index < 0) {
            return "";
        }
        return filename.substring(prefix, index + seperatorAdd);
    }
    
    /**
     * 返回给定文件路径的全路径名。
     * <p />
     * 该方法返回的路径名最后包含目录分隔符。
     * 
     * <pre>
     * C:\a\b\c.txt  --&gt; "C:\\a\\b\\"
     * ~/a/b/c.txt   --&gt; "~/a/b/"
     * a.txt         --&gt; ""
     * a/b/c         --&gt; "a/b/"
     * a/b/c/        --&gt; "a/b/c/"
     * C:            --&gt; "C:"
     * C:\           --&gt; "C:\\"
     * ~             --&gt; "~/"
     * ~/            --&gt; "~/"
     * ~user         --&gt; "~user/"
     * ~user/        --&gt; "~user/"
     * </pre>
     * 
     * @param filename 给定的文件路径。
     * @return 给定文件路径的全路径名。
     */
    public static String getFullPath(String filename) {
        return doGetFullPath(filename, true);
    }
    
    /**
     * 返回给定文件路径的全路径名。
     * <p />
     * 该方法返回的路径名最后不包含目录分隔符。
     * 
     * <pre>
     * C:\a\b\c.txt  --> "C:\\a\\b"
     * ~/a/b/c.txt   --> "~/a/b"
     * a.txt         --> ""
     * a/b/c         --> "a/b"
     * a/b/c/        --> "a/b/c"
     * C:            --> "C:"
     * C:\           --> "C:\\"
     * ~             --> "~"
     * ~/            --> "~"
     * ~user         --> "~user"
     * ~user/        --> "~user"
     * </pre>
     * 
     * filename 给定的文件路径。
     * @return 给定文件路径的全路径名。
     */
    public static String getFullPathNoEndSeperator(String filename) {
        return doGetFullPath(filename, false);
    }
    
    private static String doGetFullPath(String filename, boolean includeSeperator) {
        if(filename == null) {
            return null;
        }
        int prefix = getPrefixLength(filename);
        if(prefix < 0) {
            return null;
        }
        if(prefix >= filename.length()) {
            if(includeSeperator) {
                return getPrefix(filename);
            } else {
                return filename;
            }
        }
        int index = lastIndexOfSeperator(filename);
        if(index < 0) {
            return filename.substring(0, prefix);
        }
        int end = index + (includeSeperator ? 1 : 0);
        return filename.substring(0, end);
    }
    
    /**
     * 返回给定文件路径的文件名部份。
     * 
     * <pre>
     * null          --&gt; null
     * filename.log  --&gt; "filename.log"
     * a/b/c.log     --&gt; "c.log"
     * a/b/c         --&gt; "c"
     * a/b/c/        --&gt; ""
     * </pre>
     * 
     * @param filename 给定的文件路径。
     * @return 给定文件路径的文件名部份。
     */
    public static String getName(String filename) {
        if(filename == null) {
            return null;
        }
        int idx = lastIndexOfSeperator(filename);
        return filename.substring(idx + 1);
    }
    
    /**
     * 返回给定文件路径的基本文件名。
     * 
     * <pre>
     * null          --&gt; null
     * filename.log  --&gt; "filename"
     * a/b/c.log     --&gt; "c"
     * a/b/c         --&gt; "c"
     * a/b/c/        --&gt; ""
     * </pre>
     * 
     * @param filename 给定的文件路径。
     * @return 给定文件路径的基本文件名。
     */
    public static String getBaseName(String filename) {
        return removeExtension(getName(filename));
    }

    /**
     * 返回给定的文件路径的扩展名。
     * <p />
     * 该方法返回文件路径的最后一个扩展名分隔符 {@code "."} 后面的部份，并且 {@code "."} 后面没有目录分隔符。
     * 
     * <pre>
     * null          --&gt; null
     * filename.log  --&gt; "log"
     * a/b/c.mp3     --&gt; "mp3"
     * a/b/c         --&gt; ""
     * a/b.mp3/c     --&gt; ""
     * </pre>
     * 
     * @param filename 给定的文件路径。
     * @return 给定文件路径表示的文件的扩展名，如果不存在，则返回空字符串。
     */
    public static String getExtension(String filename) {
        if (filename == null) {
            return null;
        }
        int idx = indexOfExtension(filename);
        if (idx == -1) {
            return EMPTY;
        } else {
            return filename.substring(idx + 1);
        }
    }

    /**
     * 去除给定的文件路径的扩展名。
     * <p />
     * 该方法返回文件路径的最后一个扩展名分隔符 {@code "."} 前面的部份，并且 {@code "."} 后面没有目录分隔符。
     * 
     * <pre>
     * null          --&gt; null
     * filename.log  --&gt; "filename"
     * a/b/c.mp3     --&gt; "a/b/c"
     * a/b/c         --&gt; "a/b/c"
     * a/b.mp3/c     --&gt; "a/b.mp3/c"
     * </pre>
     * 
     * @param filename
     * @return
     */
    public static String removeExtension(String filename) {
        if (filename == null) {
            return null;
        }
        int idx = indexOfExtension(filename);
        if (idx == -1) {
            return EMPTY;
        } else {
            return filename.substring(0, idx);
        }
    }

    /**
     * 检测给定的文件路径的扩展名是否等于指定的扩展名。
     * <p />
     * 该方法检测扩展名在所有平台中都是区分大小写的。
     * 
     * @param filename 给定的文件路径。
     * @param extension 给定的扩展名。
     * @return 如果给定的扩展名是给定文件路径的扩展名，则返回 {@code true}，否则为 {@code false}。
     */
    public static boolean isExtension(String filename, String extension) {
        if (filename == null) {
            return false;
        }
        if (extension == null || extension.length() == 0) {
            return indexOfExtension(filename) == -1;
        }
        String ext = getExtension(filename);
        return ext.equals(extension);
    }
    
    /**
     * 检测给定的文件路径的扩展名是否包含在给定的扩展名数组中。
     * 
     * @param filename 给定的文件路径。
     * @param extensions 给定的一组扩展名。
     * @return 如果给定的扩展名数组中包含给定的文件路径的扩展名，则返回 {@code true}，否则为 {@code false}。
     */
    public static boolean isExtension(String filename, String[] extensions) {
        if(filename == null) {
            return false;
        }
        if(extensions == null || extensions.length == 0) {
            return indexOfExtension(filename) == -1;
        }
        String fileExt = getExtension(filename);
        for(int i = 0; i < extensions.length; i++) {
            if(fileExt.equals(extensions[i])) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * 检测给定的文件路径的扩展名是否包含在给定的扩展名集合中。
     * 
     * @param filename 给定的文件路径。
     * @param extensions 给定的扩展名集合。
     * @return 如果给定的扩展名集合中包含给定的文件路径的扩展名，则返回 {@code true}，否则为 {@code false}。
     */
    public static boolean isExtension(String filename, Collection<String> extensions) {
        if(filename == null) {
            return false;
        }
        if(extensions == null || extensions.isEmpty()) {
            return indexOfExtension(filename) == -1;
        }
        String fileExt = getExtension(filename);
        for(Iterator<String> it = extensions.iterator(); it.hasNext();) {
            if(fileExt.equals(it.next())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 返回目录分隔符在给定的文件路径中最后一次出现的索引。
     * <p />
     * 该方法中的目录分隔符视当前平台的操作系统而定。 如果给定的文件路径为 {@code null} 或空字符串，则返回 -1。
     * 
     * @param filename 给定的文件路径。
     * @return 给定的文件路径中最后一次出现目录分隔符的索引，如果未出现分隔符，则返回 -1。
     */
    public static int lastIndexOfSeperator(String filename) {
        if (isNullOrEmpty(filename)) {
            return -1;
        }
        int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR);
        int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR);
        return Math.max(lastUnixPos, lastWindowsPos);
    }

    /**
     * 返回文件扩展名分隔符 "." 在给定的文件路径中最后一次出现的索引。
     * <p />
     * 该方法同时检查文件扩展名分隔符 {@code "."} 最后一次出现后是否还存在目录分隔符。
     * 
     * <pre>例如，如果存在这些路径：
     * String filePath1 = "D:\\My Musics\\Musics.mp3\\The Eagles";
     * String filePath2 = "~user/My Musics/Musics.mp3/The Eagles";
     * FilenameUtils.indexOfExtension(filePath1); // 返回 -1
     * FilenameUtils.indexOfExtension(filePath2); // 返回 -1
     * </pre>
     * 
     * @param filename 给定文件路径。
     * @return 给定的文件路径中最后一次出现文件扩展名分隔符 "." 的索引，如果未出现，则返回 -1。
     */
    public static int indexOfExtension(String filename) {
        if (isNullOrEmpty(filename)) {
            return -1;
        }
        int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR);
        int lastSeperator = lastIndexOfSeperator(filename);
        return (lastSeperator > extensionPos ? -1 : extensionPos);
    }
}
